/*
 * Copyright (c) 2015, Nordic Semiconductor
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Version 1.3.3

// Changelog

// Version 1.3.3
//   - increased MAX_NO_ARGS to 10

// Version 1.3.2
// 	- bug setting app arg
//	- pulled provider column names out of function

// For usage examples see http://tasker.dinglisch.net/invoketasks.html

package net.dinglisch.android.tasker;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.PatternMatcher;
import android.os.Process;
import android.util.Log;

public class TaskerIntent extends Intent {

	// 3 Tasker versions
	public final static String TASKER_PACKAGE = "net.dinglisch.android.tasker";
	public final static String TASKER_PACKAGE_MARKET = TASKER_PACKAGE + "m";
	public final static String TASKER_PACKAGE_CUPCAKE = TASKER_PACKAGE + "cupcake";

	// Play Store download URLs
	public final static String MARKET_DOWNLOAD_URL_PREFIX = "market://details?id=";
	private final static String TASKER_MARKET_URL = MARKET_DOWNLOAD_URL_PREFIX + TASKER_PACKAGE_MARKET;
	private final static String TASKER_MARKET_URL_CUPCAKE = MARKET_DOWNLOAD_URL_PREFIX + TASKER_PACKAGE_CUPCAKE;

	// Direct-purchase version
	private final static String TASKER_DOWNLOAD_URL = "http://tasker.dinglisch.net/download.html";

	// Intent actions
	public final static String ACTION_TASK = TASKER_PACKAGE + ".ACTION_TASK";
	public final static String ACTION_TASK_COMPLETE = TASKER_PACKAGE + ".ACTION_TASK_COMPLETE";
	public final static String ACTION_TASK_SELECT = TASKER_PACKAGE + ".ACTION_TASK_SELECT";

	// Intent parameters
	public final static String EXTRA_ACTION_INDEX_PREFIX = "action";
	public final static String TASK_NAME_DATA_SCHEME = "task";
	public final static String EXTRA_TASK_NAME = "task_name";
	public final static String EXTRA_TASK_PRIORITY = "task_priority";
	public final static String EXTRA_SUCCESS_FLAG = "success";
	public final static String EXTRA_VAR_NAMES_LIST = "varNames";
	public final static String EXTRA_VAR_VALUES_LIST = "varValues";
	public final static String EXTRA_TASK_OUTPUT = "output";

	// Content provider columns
	public static final String PROVIDER_COL_NAME_EXTERNAL_ACCESS = "ext_access";
	public static final String PROVIDER_COL_NAME_ENABLED = "enabled";

	// DEPRECATED, use EXTRA_VAR_NAMES_LIST, EXTRA_VAR_VALUES_LIST
	public final static String EXTRA_PARAM_LIST = "params";

	// Intent data

	public final static String TASK_ID_SCHEME = "id";

	// For particular actions

	public final static String DEFAULT_ENCRYPTION_KEY = "default";
	public final static String ENCRYPTED_AFFIX = "tec";
	public final static int MAX_NO_ARGS = 10;

	// Bundle keys
	// Only useful for Tasker
	public final static String ACTION_CODE = "action";
	public final static String APP_ARG_PREFIX = "app:";
	public final static String ICON_ARG_PREFIX = "icn:";
	public final static String ARG_INDEX_PREFIX = "arg:";
	public static final String PARAM_VAR_NAME_PREFIX = "par";

	// Misc
	private final static String PERMISSION_RUN_TASKS = TASKER_PACKAGE + ".PERMISSION_RUN_TASKS";

	private final static String ACTION_OPEN_PREFS = TASKER_PACKAGE + ".ACTION_OPEN_PREFS";
	public final static String EXTRA_OPEN_PREFS_TAB_NO = "tno";
	private final static int MISC_PREFS_TAB_NO = 3; // 0 based

	// To query whether Tasker is enabled and external access is enabled
	private final static String TASKER_PREFS_URI = "content://" + TASKER_PACKAGE + "/prefs";

	private final static int CUPCAKE_SDK_VERSION = 3;

	// result values for TestSend

	// NotInstalled: Tasker package not found on device
	// NoPermission: calling app does not have permission PERMISSION_RUN_TASKS
	// NotEnabled: Tasker is not enabled
	// AccessBlocked: user prefs disallow external access
	// NoReceiver: Tasker has not created a listener for external access (probably a Tasker bug)
	// OK: you should be able to send a task to run. Still need to listen for result 
	//     for e.g. task not found

	public static enum Status {
		NotInstalled,
		NoPermission,
		NotEnabled,
		AccessBlocked,
		NoReceiver,
		OK
	}

	// -------------------------- PRIVATE VARS ---------------------------- //

	private final static String TAG = "TaskerIntent";

	private final static String EXTRA_INTENT_VERSION_NUMBER = "version_number";
	private final static String INTENT_VERSION_NUMBER = "1.1";

	// Inclusive values
	private final static int MIN_PRIORITY = 0;
	private final static int MAX_PRIORITY = 10;

	// For generating random names
	private static Random rand = new Random();

	// Tracking state
	private int actionCount = 0;
	private int argCount;

	// -------------------------- PUBLIC METHODS ---------------------------- //

	public static int getMaxPriority() {
		return MAX_PRIORITY;
	}

	public static boolean validatePriority(int pri) {
		return ((pri >= MIN_PRIORITY) || (pri <= MAX_PRIORITY));
	}

	// Tasker has different package names for Play Store and non- versions
	// for historical reasons

	public static String getInstalledTaskerPackage(Context context) {

		String foundPackage = null;

		try {
			context.getPackageManager().getPackageInfo(TASKER_PACKAGE, 0);
			foundPackage = TASKER_PACKAGE;
		} catch (PackageManager.NameNotFoundException e) {
		}

		try {
			context.getPackageManager().getPackageInfo(TASKER_PACKAGE_MARKET, 0);
			foundPackage = TASKER_PACKAGE_MARKET;
		} catch (PackageManager.NameNotFoundException e) {
		}

		return foundPackage;
	}

	// test we can send a TaskerIntent to Tasker
	// use *before* sending an intent
	// still need to test the *result after* sending intent

	public static Status testStatus(Context c) {

		Status result;

		if (!taskerInstalled(c))
			result = Status.NotInstalled;
		else if (!havePermission(c))
			result = Status.NoPermission;
		else if (!TaskerIntent.prefSet(c, PROVIDER_COL_NAME_ENABLED))
			result = Status.NotEnabled;
		else if (!TaskerIntent.prefSet(c, PROVIDER_COL_NAME_EXTERNAL_ACCESS))
			result = Status.AccessBlocked;
		else if (!new TaskerIntent("").receiverExists(c))
			result = Status.NoReceiver;
		else
			result = Status.OK;

		return result;
	}

	// Check if Tasker installed 

	public static boolean taskerInstalled(Context context) {
		return (getInstalledTaskerPackage(context) != null);
	}

	// Use with startActivity to retrieve Tasker from Android market
	public static Intent getTaskerInstallIntent(boolean marketFlag) {

		return new Intent(Intent.ACTION_VIEW, Uri.parse(marketFlag ? ((SDKVersion() == CUPCAKE_SDK_VERSION) ? TASKER_MARKET_URL_CUPCAKE : TASKER_MARKET_URL) : TASKER_DOWNLOAD_URL));
	}

	public static int SDKVersion() {
		try {
			Field f = android.os.Build.VERSION.class.getField("SDK_INT");
			return f.getInt(null);
		} catch (Exception e) {
			return CUPCAKE_SDK_VERSION;
		}
	}

	public static IntentFilter getCompletionFilter(String taskName) {

		IntentFilter filter = new IntentFilter(TaskerIntent.ACTION_TASK_COMPLETE);

		filter.addDataScheme(TASK_NAME_DATA_SCHEME);
		filter.addDataPath(taskName, PatternMatcher.PATTERN_LITERAL);

		return filter;
	}

	public static Intent getTaskSelectIntent() {
		return new Intent(ACTION_TASK_SELECT).setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_HISTORY);
	}

	// public access deprecated, use TaskerIntent.testSend() instead

	public static boolean havePermission(Context c) {
		return c.checkPermission(PERMISSION_RUN_TASKS, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
	}

	// Get an intent that will bring up the Tasker prefs screen with the External Access control(s)
	// Probably you want to use startActivity or startActivityForResult with it

	public static Intent getExternalAccessPrefsIntent() {
		return new Intent(ACTION_OPEN_PREFS).putExtra(EXTRA_OPEN_PREFS_TAB_NO, MISC_PREFS_TAB_NO);
	}

	// ------------------------------------- INSTANCE METHODS ----------------------------- //

	public TaskerIntent() {
		super(ACTION_TASK);
		setRandomData();
		putMetaExtras(getRandomString());
	}

	public TaskerIntent(String taskName) {
		super(ACTION_TASK);
		setRandomData();
		putMetaExtras(taskName);
	}

	public TaskerIntent setTaskPriority(int priority) {

		if (validatePriority(priority))
			putExtra(EXTRA_TASK_PRIORITY, priority);
		else
			Log.e(TAG, "priority out of range: " + MIN_PRIORITY + ":" + MAX_PRIORITY);

		return this;
	}

	// Sets subsequently %par1, %par2 etc
	public TaskerIntent addParameter(String value) {

		int index = 1;

		if (getExtras().containsKey(EXTRA_VAR_NAMES_LIST))
			index = getExtras().getStringArrayList(EXTRA_VAR_NAMES_LIST).size() + 1;

		Log.d(TAG, "index: " + index);

		addLocalVariable("%" + PARAM_VAR_NAME_PREFIX + index, value);

		return this;
	}

	// Arbitrary specification of (local) variable names and values
	public TaskerIntent addLocalVariable(String name, String value) {

		ArrayList<String> names, values;

		if (hasExtra(EXTRA_VAR_NAMES_LIST)) {
			names = getStringArrayListExtra(EXTRA_VAR_NAMES_LIST);
			values = getStringArrayListExtra(EXTRA_VAR_VALUES_LIST);
		} else {
			names = new ArrayList<>();
			values = new ArrayList<>();

			putStringArrayListExtra(EXTRA_VAR_NAMES_LIST, names);
			putStringArrayListExtra(EXTRA_VAR_VALUES_LIST, values);
		}

		names.add(name);
		values.add(value);

		return this;
	}

	public TaskerIntent addAction(int code) {

		actionCount++;
		argCount = 1;

		Bundle actionBundle = new Bundle();

		actionBundle.putInt(ACTION_CODE, code);

		// Add action bundle to intent
		putExtra(EXTRA_ACTION_INDEX_PREFIX + Integer.toString(actionCount), actionBundle);

		return this;
	}

	// string arg
	public TaskerIntent addArg(String arg) {

		Bundle b = getActionBundle();

		if (b != null)
			b.putString(ARG_INDEX_PREFIX + Integer.toString(argCount++), arg);

		return this;
	}

	// int arg
	public TaskerIntent addArg(int arg) {
		Bundle b = getActionBundle();

		if (b != null)
			b.putInt(ARG_INDEX_PREFIX + Integer.toString(argCount++), arg);

		return this;
	}

	// boolean arg
	public TaskerIntent addArg(boolean arg) {
		Bundle b = getActionBundle();

		if (b != null)
			b.putBoolean(ARG_INDEX_PREFIX + Integer.toString(argCount++), arg);

		return this;
	}

	// Application arg
	public TaskerIntent addArg(String pkg, String cls) {
		Bundle b = getActionBundle();

		if (b != null) {
			StringBuilder builder = new StringBuilder();
			builder.append(APP_ARG_PREFIX).append(pkg).append(",").append(cls);
			b.putString(ARG_INDEX_PREFIX + Integer.toString(argCount++), builder.toString()); // CHANGED: b.toString()
		}

		return this;
	}

	public IntentFilter getCompletionFilter() {
		return getCompletionFilter(getTaskName());
	}

	public String getTaskName() {
		return getStringExtra(EXTRA_TASK_NAME);
	}

	public boolean receiverExists(Context context) {
		List<ResolveInfo> recs = context.getPackageManager().queryBroadcastReceivers(this, 0);
		return ((recs != null) && (recs.size() > 0));
	}

	// -------------------- PRIVATE METHODS -------------------- //

	private String getRandomString() {
		return Long.toString(rand.nextLong());
	}

	// so that if multiple TaskerIntents are used in PendingIntents there's virtually no
	// clash chance
	private void setRandomData() {
		setData(Uri.parse(TASK_ID_SCHEME + ":" + getRandomString()));
	}

	private Bundle getActionBundle() {

		Bundle toReturn = null;

		if (argCount > MAX_NO_ARGS)
			Log.e(TAG, "maximum number of arguments exceeded (" + MAX_NO_ARGS + ")");
		else {
			String key = EXTRA_ACTION_INDEX_PREFIX + Integer.toString(actionCount);

			if (this.hasExtra(key))
				toReturn = getBundleExtra(key);
			else
				Log.e(TAG, "no actions added yet");
		}

		return toReturn;
	}

	private void putMetaExtras(String taskName) {
		putExtra(EXTRA_INTENT_VERSION_NUMBER, INTENT_VERSION_NUMBER);
		putExtra(EXTRA_TASK_NAME, taskName);
	}

	// for testing that Tasker is enabled and external access is allowed

	private static boolean prefSet(Context context, String col) {

		String[] proj = new String[] { col };

		Cursor c = context.getContentResolver().query(Uri.parse(TASKER_PREFS_URI), proj, null, null, null);

		boolean acceptingFlag = false;

		if (c == null)
			Log.w(TAG, "no cursor for " + TASKER_PREFS_URI);
		else {
			c.moveToFirst();

			if (Boolean.TRUE.toString().equals(c.getString(0)))
				acceptingFlag = true;

			c.close();
		}

		return acceptingFlag;
	}
}